void init_dtables() {
    init_gdt();
    init_idt();
}

void init_gdt() {
    gdt_ptr.limit = (sizeof(gdt_entry) * 6) - 1;
    gdt_ptr.base = (u32int)&gdt_entries;
    
    assemble_gdt();
    gdt_flush((u32int)&gdt_ptr);
    tss_flush();
}

gdt_entry create_gdt_entry(u32int base, u32int limit, u8int access, u8int granularity) {
    gdt_entry entry;
    entry.base_low = (base & 0xFFFF);
    entry.base_middle = (base >> 16) & 0xFF;
    entry.base_high = (base >> 24) & 0xFF;
    entry.limit_low = (limit & 0xFFFF);
    entry.granularity = ((limit >> 16) & 0x0F) | (granularity & 0xF0);
    entry.access = access;
    return entry;
}

gdt_entry get_gdt_entry(u32int index) {
    return gdt_entries[index];
}

void set_gdt_entry(u32int index, gdt_entry entry) {
    gdt_entries[index] = entry;
}

u32int temporary_stack[4096];

void assemble_gdt() {
    set_gdt_entry(0, create_gdt_entry(0, 0, 0, 0));
    set_gdt_entry(1, create_gdt_entry(0, 0xFFFFFFFF, 0x9A, 0xCF));
    set_gdt_entry(2, create_gdt_entry(0, 0xFFFFFFFF, 0x92, 0xCF));
    set_gdt_entry(3, create_gdt_entry(0, 0xFFFFFFFF, 0xFA, 0xCF));
    set_gdt_entry(4, create_gdt_entry(0, 0xFFFFFFFF, 0xF2, 0xCF));
    set_gdt_entry(5, create_gdt_entry_for_tss(0x10, (u32int)(&temporary_stack) + (4096*4)));
}

gdt_entry create_gdt_entry_for_tss(u16int ss0, u32int esp0) {
    memset((u8int*)&sole_tss_entry, 0, sizeof(tss_entry)); 
    sole_tss_entry.ss0 = ss0;
    sole_tss_entry.esp0 = esp0;
    // Here we set the cs, ss, ds, es, fs and gs entries in the TSS. These specify what
    // segments should be loaded when the processor switches to kernel mode. Therefore
    // they are just our normal kernel code/data segments - 0x08 and 0x10 respectively,
    // but with the last two bits set, making 0x0b and 0x13. The setting of these bits
    // sets the RPL (requested privilege level) to 3, meaning that this TSS can be used
    // to switch to kernel mode from ring 3.
    sole_tss_entry.cs   = 0x0b;
    sole_tss_entry.ss = sole_tss_entry.ds = sole_tss_entry.es = sole_tss_entry.fs = sole_tss_entry.gs = 0x13;
    
    u32int base = (u32int) &sole_tss_entry;
    u32int limit = base + sizeof(tss_entry);
    return create_gdt_entry(base, limit, 0xE9, 0x00);
}

void print_gdt_entry(gdt_entry entry) {
    put_string("Limit_Low: ");
    put_hex(entry.limit_low);
    put_string("\nBase_Low: ");
    put_hex(entry.base_low);
    put_string("\nBase_Middle: ");
    put_hex(entry.base_middle);
    put_string("\nAccess: ");
    put_hex(entry.access);
    put_string("\nGranularity: ");
    put_hex(entry.granularity);
    put_string("\nBase_High: ");
    put_hex(entry.base_high);
    put_string("\n");
}

void init_idt() {
    idt_ptr.limit = (sizeof(idt_entry) * 256) - 1;
    idt_ptr.base = (u32int)&idt_entries;
    memset((u8int*)&idt_entries, 0, sizeof(idt_entry) * 256);
    
    remap_irq_table();
    assemble_idt();
    idt_flush((u32int)&idt_ptr);
}

void create_idt_entry(u32int index, u32int base, u32int bigselector, u32int bigflags) {
    // The reason I'm having these being passed as 32bit integers
    // is that there's a lot about assembly I don't understand
    // and I couldn't figure out how to do it otherwise
    u16int selector = bigselector;
    u8int flags = bigflags;

    idt_entries[index].base_low = base & 0xFFFF;
    idt_entries[index].base_high = (base >> 16) & 0xFFFF;
    idt_entries[index].selector = selector;
    idt_entries[index].unused = 0;
    idt_entries[index].flags = flags;
}

void remap_irq_table() {
    outb(0x20, 0x11);
    outb(0xA0, 0x11);
    outb(0x21, 0x20);
    outb(0xA1, 0x28);
    outb(0x21, 0x04);
    outb(0xA1, 0x02);
    outb(0x21, 0x01);
    outb(0xA1, 0x01);
    outb(0x21, 0x0);
    outb(0xA1, 0x0);
}

